﻿using System;
using System.Net;
using System.Net.Sockets;
using System.Threading;
using System.Collections.Generic;
using CSEngine;

namespace Server
{
    public class GameServer: TcpServer
    {
        private Dictionary<TcpClient, GameData> games;
        //private GameData gameData;

        public event ServerInputDelegate InvalidClientInput;

        public GameServer()
            : base()
        {
            ClientDisconnected += GameServer_ClientDisconnected;
            AcceptConnection += GameServer_AcceptConnection;
            ClientInput += GameServer_ClientInput;
            InvalidClientInput += TcpServer.DoNothing;
            games = new Dictionary<TcpClient, GameData>();
        }

        void GameServer_ClientDisconnected(object sender, ServerEventArgs e)
        {
            games.Remove(e.Client);
        }

        private void GameServer_AcceptConnection(object sender, ServerEventArgs e)
        {
            games.Add(e.Client, new GameData());
            SendLine(e.Client, "WELCOME");
        }

        private void GameServer_ClientInput(object sender, ServerInputArgs e)
        {
            String[] words = e.Message.Trim().Split(' ','\t','\n');
            foreach (String word in words)
                ProcessInput(word, e);

            SendLine(e.Client, "OK");
        }

        private void ProcessInput(string word, ServerEventArgs args)
        {
            Expression expr;
            if (!Expression.TryParse(word, out expr))
            {
                InvalidClientInput.Invoke(this, new ServerInputArgs(args.Client, word));
                return;
            }

            switch (expr.Type)
            {
                case Expression.ExprType.COMMAND:
                    ProcessCommand((Command)expr.Value, args.Client);
                    break;
                case Expression.ExprType.COMMENT:
                    break;
                case Expression.ExprType.MOVE:
                    ProcessMove((Move)expr.Value, args.Client);
                    break;
                default:
                    throw new NotSupportedException("Invalid Expression Type");
            }
        }

        private void ProcessMove(Move mv, TcpClient sender)
        {
            GameData gd = games[sender];
            if (!gd.InProgress)
                throw new InvalidOperationException("Game not in progress");
            else if (gd.RemoteMove.HasValue)
                throw new InvalidOperationException("RemoteMove already has a value");

            gd.RemoteMove = mv;
        }

        private void ProcessCommand(Command com, TcpClient sender)
        {
            GameData gameData = games[sender];
            switch (com.CommandName)
            {
                case Command.CommandType.AI_AI:
                    throw new NotImplementedException("AI-AI Command Not Yet Implemented");
                case Command.CommandType.DISPLAY:
                    gameData.Display = !gameData.Display;
                    break;
                case Command.CommandType.EASY:
                    gameData.AI = GetEasyAI();
                    break;
                case Command.CommandType.EXIT:
                    throw new NotImplementedException("EXIT Command Not Yet Implemented");
                case Command.CommandType.HARD:
                    gameData.AI = GetHardAI();
                    break;
                case Command.CommandType.HUMAN_AI:
                    StartHumanAIGame(sender);
                    break;
                case Command.CommandType.MEDIUM:
                    gameData.AI = GetMediumAI();
                    break;
                case Command.CommandType.UNDO:
                    // TODO - This does not appear to work?
                    // TODO - Error Checking - If the game type is remote, this will
                    //  cause a casting error
                    // Additionally, GetGame() can throw an InvalidOperationException
                    //  if there is no game in progress
                    ((LocalGameData)gameData).GetGame().UndoLastMove();
                    break;
                default:
                    throw new NotSupportedException("Command Not Supported");
            }
        }

        private void StartHumanAIGame(TcpClient sender)
        {
            GameData gameData = games[sender];
            if (gameData.InProgress)
                throw new InvalidOperationException("Game already in progress");

            LocalGameData gd = new LocalGameData(sender.GetStream(), gameData.AI);
            games[sender] = gd;
            gd.StartGame();
        }

        public static AI GetDefaultAI()
        {
            return GetEasyAI();
        }

        public static AI GetEasyAI()
        {
            return new EasyAI();
        }

        public static AI GetMediumAI()
        {
            throw new NotImplementedException("Medium AI not yet implemented");
        }

        public static AI GetHardAI()
        {
            throw new NotImplementedException("Hard AI not yet implemented");
        }
    }
}
